﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Xml.Serialization;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;
using DE.Sdc.SharePoint.SPContentStructure.Common;


namespace DE.Sdc.SharePoint.SPContentStructure.Import
{
    [Serializable]
    [XmlType(AnonymousType = true, Namespace = DeploymentConstants.Namespace)]
    [XmlRoot(Namespace = DeploymentConstants.Namespace, IsNullable = false)]
    public class ItemProperties : List<Property>
    {
        
        public ItemProperties(IHierarchicalElement item)
        {
            Item = item;
        }

        /// <remarks/>
        [XmlIgnore]
        public IHierarchicalElement Item { get; set; }
        /// <summary>
        /// Modifies the properties of an item
        /// </summary>
        /// <param name="item">The list item that properties should changed</param>
        protected internal void Modify(SPListItem item)
        {
            if (item == null)
                return;

            Hashtable properties = GetAsHashtable(item.Web.Locale);
            Item currentItem = Item as Item;

            if (item.Fields.ContainsField("Title"))
            {
                if ((string)item["Title"] != Item.Title && !properties.ContainsKey("Title"))
                    properties["Title"] = Item.Title;

                if (currentItem != null && currentItem.File != null)
                {
                    if (currentItem.File.OverwriteFileTitleWithItemTitle && !properties.ContainsKey("Title"))
                        properties["Title"] = Item.Title;
                    else if (!currentItem.File.OverwriteFileTitleWithItemTitle)
                    {
                        // Hack: wird gesetzt, damit eine Eigenschaft geändert wird, z.B. beim Bulkimport, wenn keine Properties vorhanden sind, 
                        // da sonst das SystemUpdate weiter unten sonst nicht dazu führt, dass der Item / File Status richtig gesetzt wird.
                        if (!String.IsNullOrEmpty(item.Title) && 
                            // Ermöglicht das der Title auf einem vorhandenen Item geändert werden kann. 
                            // Dafür muss der Title des Items der alte sein und über die Properties muss der neue Title angegeben werden.
                            !properties.ContainsKey("Title"))
                            properties["Title"] = item.Title;
                        else
                            properties["Title"] = properties["Title"];
                    }
                }
            }

            if (properties.Count == 0)
                return;

            if (properties.ContainsKey("ContentTypeId"))
            {
                var contentTypeId =
                    item.ParentList.ContentTypes.BestMatch(new SPContentTypeId(properties["ContentTypeId"].ToString()));
                //item.ContentType.Name = item.ParentList.ContentTypes[contentTypeId].Name;
                item["ContentTypeId"] = contentTypeId;
            }

            bool propertyUpdated = false;

            foreach (string propertyName in properties.Keys)
            {
                if (propertyName.Equals("ContentTypeId"))
                    continue;

                propertyUpdated |= SetPropertyValue(item, propertyName, properties[propertyName]);
            }

            if (propertyUpdated)
            {
                if (Item is Item && ((Item) Item).File != null && item.File != null)
                    ((Item) Item).File.Checkout(item.File);
                //if (Item is Attachment && item.File != null)
                //    ((Attachment)(Item)).Checkout(item.File);
                item.UpdateOverwriteVersion();
            }

        }

        public static bool SetPropertyValue(SPListItem item, string propertyName, object propertyValue)
        {
            var propertyUpdated = false;

            //Bedingung angepasst, damit auch Properties gesetzt werden können, die nicht zum ContentType gehören (z.B. bei Migration Werte auf string.Empty setzen)
            //if ((item.ContentType != null && item.ContentType.Fields.ContainsField(propertyName)) ||
            //    (item.ContentType == null && item.Fields.ContainsField(propertyName)))
            if (item.Fields.ContainsField(propertyName))
            {
                var field = item.Fields.GetField(propertyName);
                var currentPropertyValue = item[propertyName];

                if (propertyValue != null && propertyValue.GetType().IsEnum)
                {
                    object newPropertyValue = (int)propertyValue;
                    if (currentPropertyValue != newPropertyValue &&
                        currentPropertyValue != null &&
                        currentPropertyValue.Equals(newPropertyValue))
                        item[propertyName] = newPropertyValue;
                    propertyUpdated = true;
                }
                else if(propertyValue != null && field is SPFieldBoolean)
                {
                    bool outValue;
                    if (Boolean.TryParse(propertyValue.ToString(), out outValue))
                    {
                        item[field.InternalName] = propertyValue;
                        propertyUpdated = true;
                    }
                    else
                    {
                        var stringValue = propertyValue.ToString().Trim();
                        item[field.InternalName] = stringValue.Equals("yes", StringComparison.OrdinalIgnoreCase) ||
                                                   stringValue.Equals("ja", StringComparison.OrdinalIgnoreCase) ||
                                                   stringValue == "1";
                        propertyUpdated = true;
                    }
                }
                else if (propertyValue != null && field is SPFieldUser)
                {
                    currentPropertyValue = currentPropertyValue != null ? field.GetFieldValue(currentPropertyValue.ToString()) : null;
                    object newPropertyValue = GetUserValue(item, propertyName, propertyValue);

                    if ((currentPropertyValue != newPropertyValue && (newPropertyValue == null || currentPropertyValue == null)) || 
                        (newPropertyValue != null && currentPropertyValue != null && newPropertyValue is SPFieldUserValue &&
                        ((SPFieldUserValue)newPropertyValue).User.LoginName != ((SPFieldUserValue)currentPropertyValue).User.LoginName))
                    {
#if SP_07
                        //item.SetFieldValueUser(field.Title, newPropertyValue == null ? null : newPropertyValue.ToString());
                        item[field.InternalName] = newPropertyValue;
#else
                        field.ParseAndSetValue(item, newPropertyValue == null ? null : newPropertyValue.ToString());
#endif
                        propertyUpdated = true;
                    }

                    if ((currentPropertyValue != newPropertyValue && (newPropertyValue == null || currentPropertyValue == null)) ||
                        (newPropertyValue != null && currentPropertyValue != null && newPropertyValue is SPFieldUserValueCollection &&
                        ((SPFieldUserValueCollection)newPropertyValue).Count != ((SPFieldUserValueCollection)currentPropertyValue).Count))
                    {
#if SP_07
                        //item.SetFieldValueUser(field.Title, newPropertyValue == null ? null : newPropertyValue.ToString());
                        item[field.InternalName] = newPropertyValue;
#else
                        field.ParseAndSetValue(item, newPropertyValue == null ? null : newPropertyValue.ToString());
#endif
                        propertyUpdated = true;
                    }
                }
                else if (propertyValue != null && item.Fields.GetField(propertyName) is SPFieldLookup)
                {
                    object newPropertyValue = GetLookupValue(item, propertyName, propertyValue);

                    if ((currentPropertyValue != newPropertyValue && (newPropertyValue == null || currentPropertyValue == null)) ||
                        (newPropertyValue != null && currentPropertyValue != null && newPropertyValue is SPFieldLookupValue &&
                        newPropertyValue.ToString() != currentPropertyValue.ToString()))
                    {
                        item[propertyName] = newPropertyValue;
                        propertyUpdated = true;
                    }

                    if ((currentPropertyValue != newPropertyValue && (newPropertyValue == null || currentPropertyValue == null)) ||
                        (newPropertyValue != null && currentPropertyValue != null && newPropertyValue is SPFieldLookupValueCollection &&
                        ((SPFieldLookupValueCollection)newPropertyValue).Count != ((SPFieldLookupValueCollection)currentPropertyValue).Count))
                    {
                        item[propertyName] = newPropertyValue;
                        propertyUpdated = true;
                    }
                }
                else if (propertyValue != currentPropertyValue &&
                            (propertyValue != null && !propertyValue.Equals(currentPropertyValue) ||
                             currentPropertyValue != null && !currentPropertyValue.Equals(propertyValue)))
                {
                    item[propertyName] = propertyValue;
                    propertyUpdated = true;
                }
            }
            return propertyUpdated;
        }

        private static object GetUserValue(SPListItem item, string propertyName, object propertyValue)
        {
            SPFieldLookup field = ((SPFieldUser)item.Fields.GetField(propertyName));
            List<string> users = new List<string>(((string)propertyValue).Split(new char[] { ';' }));

            SPFieldUserValueCollection fieldUserValueCollection = null;
            SPFieldUserValue fieldUserValue = null;

            if (field.AllowMultipleValues)
                fieldUserValueCollection = new SPFieldUserValueCollection();

            if (!field.AllowMultipleValues && users.Count > 1)
                throw new ContentStructureException(string.Format("Multiple lookup values are specified for the field '{0}' in list '{1}'. Please make sure that the field allwos multiple lookup values.", field.Title, field.ParentList.Title));

            for (int index = 0; index < users.Count; index++)
            {
                string loginName = users[index];
                try
                {
                    SPUser user = item.Web.EnsureUser(loginName);
                    fieldUserValue = new SPFieldUserValue(item.Web, user.ID, user.Name);
                }
                catch (SPException)
                {
                    continue;
                }
                if (fieldUserValueCollection != null)
                    fieldUserValueCollection.Add(fieldUserValue);
            }


            //Darf nicht in die Kurzschreibweise umgewandelt werden,
            //da sonst die Collection auf object gecastet werden muss und das dann nicht mehr funktioniert
            if (fieldUserValueCollection != null)
                return fieldUserValueCollection;

            return fieldUserValue;
        }

        private static object GetLookupValue(SPListItem item, string propertyName, object propertyValue)
        { 
            SPFieldLookup field = ((SPFieldLookup)item.Fields.GetField(propertyName));
            String queryFormat = "<Eq><FieldRef Name='{0}' /><Value Type='Text'>{1}</Value></Eq>";
            String queryText = null;
            SPList lookupList = item.ParentList.ParentWeb.Lists[new Guid(field.LookupList)];

            string[] lookupValues = propertyValue.ToString().Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
            var lookupItems = new List<SPListItem>();
            //Items ohne Ordnerangabe ermitteln (RecursiveAll)
            for (int index = 0; index < lookupValues.Length; index++)
            {
                string lookupValue = lookupValues[index];
                if (lookupValue.Contains("/"))
                    continue;

                if (index > 0)
                    queryText = "<Or>" + queryText;
                queryText += string.Format(queryFormat, field.LookupField, lookupValue.Trim());
                if (index > 0)
                    queryText += "</Or>";
            }
            if (!string.IsNullOrEmpty(queryText))
            {
                queryText = "<Where>" + queryText + "</Where>";
                lookupItems = lookupList.GetItems(new SPQuery { Query = queryText, ViewAttributes = "Scope=\"RecursiveAll\"" }).Cast<SPListItem>().ToList();
            }
            
            //(Items mit Ordnerangabe ermitteln
            for (int index = 0; index < lookupValues.Length; index++)
            {
                string lookupValue = lookupValues[index];
                if (!lookupValue.Contains("/"))
                    continue;
                var folderUrl = lookupValue.Substring(0, lookupValue.LastIndexOf("/"));
                var folder = lookupList.ParentWeb.GetFolder(SPUrlUtility.CombineUrl(lookupList.RootFolder.ServerRelativeUrl, folderUrl));
                queryText = "<Where>" + string.Format(queryFormat, field.LookupField, lookupValue.Substring(lookupValue.LastIndexOf("/") + 1)) + "</Where>";
                lookupItems.AddRange(lookupList.GetItems(new SPQuery { Folder = folder, Query = queryText }).Cast<SPListItem>());
            }
            
            if (lookupItems.Count > 0)
            {
                SPFieldLookupValueCollection fieldLookupValueCollection = null;
                SPFieldLookupValue fieldLookupValue = null;

                if (field.AllowMultipleValues)
                    fieldLookupValueCollection = new SPFieldLookupValueCollection();

                if (!field.AllowMultipleValues && lookupItems.Count > 1)
                    throw new ContentStructureException(string.Format("Multiple lookup values are specified for the field '{0}' in list '{1}'. Please make sure that the field allwos multiple lookup values.", field.Title, field.ParentList.Title));

                foreach (SPListItem lookupItem in lookupItems)
                {
                    int lookupId = Convert.ToInt32(lookupItem[SPBuiltInFieldId.ID]);

                    object lookupValue;

                    // The LookupField property of the field may by a GUID, so we try to
                    // get the value using the LookupField property as if it were the field name.
                    // If this fails, we get an ArgumentException, so we then try to get the
                    // value using a GUID. If this fails... then something is terribly wrong :)

                    try
                    {
                        lookupValue = lookupItem[field.LookupField];                        
                    }
                    catch (ArgumentException)
                    {
                        lookupValue = lookupItem[new Guid(field.LookupField)];
                    }
                    
                    fieldLookupValue = new SPFieldLookupValue(lookupId, (String)lookupValue);

                    if (fieldLookupValueCollection != null)
                        fieldLookupValueCollection.Add(fieldLookupValue);
                }

                //Darf nicht in die Kurzschreibweise umgewandelt werden,
                //da sonst die Collection auf object gecastet werden muss und das dann nicht mehr funktioniert
                if (fieldLookupValueCollection != null)
                    return fieldLookupValueCollection;
                
                return fieldLookupValue;
            }

            return null;
        }

        public Hashtable GetAsHashtable(CultureInfo culture)
        {
            var properties = new Hashtable();

            for (int index = 0; index < Count; index++)
            {
                Property property = this[index];
                properties.Add(DeploymentHelper.Resolve(property.Name, culture), DeploymentHelper.Resolve(property.Value, culture));
            }

            return properties;
        }

        //public void Resolve(CultureInfo culture)
        //{
        //    for (int index = 0; index < Count; index++)
        //    {
        //        Property property = this[index];
        //        property.Name = DeploymentHelper.Resolve(property.Name, culture);
        //        property.Value = DeploymentHelper.Resolve(property.Value, culture);
        //    }
        //}
    }
}
